Skip to content

feat: disambiguate media files by prefix via query parameter#15844

Open
tschwartz wants to merge 2 commits intopayloadcms:mainfrom
tschwartz:media-endpoint
Open

feat: disambiguate media files by prefix via query parameter#15844
tschwartz wants to merge 2 commits intopayloadcms:mainfrom
tschwartz:media-endpoint

Conversation

@tschwartz
Copy link

Add optional ?prefix= query string support to the existing /api/:collection/file/:filename endpoint to disambiguate files that share the same filename but have different storage prefixes.

When a storage prefix (e.g., UUID or folder path) is used to make S3 keys unique, multiple documents can share the same filename. The current endpoint resolves by filename alone, which can match the wrong document in both access control and file retrieval.

  • checkFileAccess accepts optional prefix and adds it to the where clause alongside existing access filters
  • getFile handler reads prefix from req.searchParams and threads it to checkFileAccess and handler params
  • getFilePrefix accepts explicitPrefix to skip the DB query when the prefix is already known from the URL
  • S3 staticHandler forwards prefix from params to getFilePrefix
  • afterRead hook appends ?prefix= to Payload-proxied URLs
  • Added unit tests for checkFileAccess and getFilePrefix, and integration tests for the endpoint'

Add optional `?prefix=` query string support to the existing
`/api/:collection/file/:filename` endpoint to disambiguate files
that share the same filename but have different storage prefixes.

When a storage prefix (e.g., UUID or folder path) is used to make
S3 keys unique, multiple documents can share the same `filename`.
The current endpoint resolves by filename alone, which can match
the wrong document in both access control and file retrieval.

- `checkFileAccess` accepts optional `prefix` and adds it to the
  `where` clause alongside existing access filters
- `getFile` handler reads `prefix` from `req.searchParams` and
  threads it to `checkFileAccess` and handler `params`
- `getFilePrefix` accepts `explicitPrefix` to skip the DB query
  when the prefix is already known from the URL
- S3 `staticHandler` forwards `prefix` from params to `getFilePrefix`
- `afterRead` hook appends `?prefix=` to Payload-proxied URLs
- Added unit tests for `checkFileAccess` and `getFilePrefix`,
  and integration tests for the endpoint'
@tschwartz tschwartz requested a review from denolfe as a code owner March 4, 2026 17:28
try {
const prefix = await getFilePrefix({ clientUploadContext, collection, filename, req })
const prefix = await getFilePrefix({
clientUploadContext,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would have been nice to have added the prefix to the clientUploadContext but the unknown type would require a bunch of checks here in addition to all the checks already in the getFilePrefix function.

],
},
},
{
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its this correct? Really unsure about this particular bit.

When a media URL already contains query parameters, appending the
imageCacheTag with '?' produces a malformed URL with double '?'.
Check for existing query parameters and use '&' as the separator
when needed, matching the pattern already used in the Thumbnail
component.
Copy link
Contributor

@GermanJablo GermanJablo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor note (non-blocking)

Only the S3 storage adapter is updated. Other storage adapters (Azure, GCS, Vercel Blob, etc.) aren't changed. This is fine since the core changes (checkFileAccess, getFile) work independently. Other adapters can be updated separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants